package com.wy._05Random;

import com.wy._05Random.Enum.CnCharacterEnum;
import com.wy._05Random.Enum.EnCharacterEnum;
import com.wy._05Random.Enum.UnicodeTypeEnum;
import com.wy._05Random.Enum.UnicodeFragment;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * 返回随机字符串
 * <p>
 * 为了避免过多的创造对象，我们在方法中传递一个Random对象，在方法内部一般不创造对象。  好像没那个必要。多了一个参数，是在多余，但是这么写会提升效率吗， 好像没什么关系。
 */
//还有一个思路，绝对随机，数量，大小等等参考因素绝对随机的方法，
public class RandomCharacter {
    /**
     * 得到一个随机的字符 (unicode 两边都包含)
     *
     * @param unicodeStart 开始的 unicode 位置  包含
     * @param unicodeEnd   结束的 unicode 位置  包含
     * @param random       random对象
     * @return 得到一个随机的字符 (其中 unicode 两边都包含)
     */
    public static String getChar(int unicodeStart, int unicodeEnd, Random random) {
        return validCodePoint(random.nextInt(unicodeEnd - unicodeStart + 1) + unicodeStart);
    }

    /**
     * 得到随机字符 根据片段对象
     *
     * @param unicodeFragment 片段对象
     * @param random          随机对象
     * @return 得到随机字符 根据片段对象
     */
    public static String getChar(UnicodeFragment unicodeFragment, Random random) {
        return getChar(unicodeFragment.getStartCoding(), unicodeFragment.getEndCoding(), random);
    }

    /**
     * @param cnCharacterEnum 汉字在unicode 添加批次的枚举
     * @param random          随机对象
     * @return 返回这个批次其中一个汉字
     */
    public static String getChar(CnCharacterEnum cnCharacterEnum, Random random) {
        return getChar(cnCharacterEnum.getUnicodeFragmentEnum().getUnicodeFragment().getStartCoding(),
                cnCharacterEnum.getUnicodeFragmentEnum().getUnicodeFragment().getEndCoding(), random);
    }

    /**
     * 得到随机的英文字母 大写或者小写
     *
     * @param enCharacterEnum 英文类型枚举
     * @param random          堆积对象
     * @return 得到随机的英文字母
     */
    public static String getChar(EnCharacterEnum enCharacterEnum, Random random) {
        return getChar(enCharacterEnum.getUnicodeFragmentEnum().getUnicodeFragment().getStartCoding(), enCharacterEnum.getUnicodeFragmentEnum().getUnicodeFragment().getEndCoding(), random);
    }

    /**
     * 根据 unicode 的类型返回一个字符
     *
     * @param unicodeTypeEnum unicode 的枚举类型
     * @param random          随机对象
     * @return 根据 unicode 的类型返回一个字符
     */
    public static String getChar(UnicodeTypeEnum unicodeTypeEnum, Random random, boolean isAbsoluteRandomness) {
        return getRandomCharByUnicodeFragmentList(unicodeTypeEnum.getUnicodeFragmentList(), random, isAbsoluteRandomness);
    }

    /**
     * 得到随机大写字母
     *
     * @param random 随机对象
     * @return 得到随机大写字母
     */
    public static String getRandomCapitalLetters(Random random) {
        return getChar(EnCharacterEnum.Capital_Letter, random);
    }

    /**
     * 得到随机小写字母
     *
     * @param random 随机对象
     * @return 得到随机小写字母
     */
    public static String getRandomLowercaseLetters(Random random) {
        return getChar(EnCharacterEnum.Lowercase_Letter, random);
    }

    /**
     * 得到随机字母，大小写全部包括
     *
     * @param random 随机对象
     * @return 得到随机字母
     */
    public static String getRandomLetters(Random random) {
        return getChar(UnicodeTypeEnum.Letter, random, true);
    }

    /**
     * 得到一个随机的数字
     *
     * @param random 随机对象
     * @return 得到一个随机的数字
     */
    public static String getRandomNumbers(Random random) {
        return getChar(UnicodeTypeEnum.Numeral, random, true);
    }

    /**
     * 得到随机的中文字符串，包括现在存在的所有中文，慎用，很多计算机无法识别对应的中文
     *
     * @param random 随机对象
     * @return 得到随机的中文字符串
     */
    public static String getRandomCn(Random random, boolean isAbsoluteRandomness) {
        return getChar(UnicodeTypeEnum.Chinese_Character, random, isAbsoluteRandomness);
    }

    /**
     * 得到随随机中文字符串，包括所有的中文
     *
     * @param length 字符串长度
     * @param random 随机对象
     * @return 得到随随机中文字符串
     */
    public static String getRandomCnStr(int length, Random random, boolean isAbsoluteRandomness) {
        StringBuilder str = new StringBuilder();
        for (int i = 0; i < length; i++) {
            String randomCn = getRandomCn(random, isAbsoluteRandomness);
            str.append(randomCn);
        }
        return str.toString();
    }

    /**
     * 得到一个随机 基本汉字
     *
     * @param random 随机对象
     * @return 得到一个随机基本的汉字
     */
    public static String getRandomBasicCn(Random random) {
        return getChar(CnCharacterEnum.Basic_Chinese_Character, random);
    }

    /**
     * 得到随机基本的中文字符 字符串
     *
     * @param length 字符串长度
     * @return 返回一定长度的字符串
     */
    public static String getRandomBasicCnStr(int length, Random random) {
        StringBuilder str = new StringBuilder();
        for (int i = 0; i < length; i++) {
            str.append(getRandomBasicCn(random));
        }
        return str.toString();
    }

    /**
     * 得到随机字符串，其中包括数字，英文字母(大小写)，中文，基本汉字。
     *
     * @param length 字符串长度
     * @param random 随机对象
     * @return 得到随机字符串
     */
    public static String getRandomStrEnCnNu(int length, Random random, boolean isAbsoluteRandomness) {
        StringBuilder str = new StringBuilder();
        List<UnicodeFragment> fragmentList = new ArrayList<>();
        List<UnicodeFragment> letterUnicodeFragmentList = UnicodeTypeEnum.Letter.getUnicodeFragmentList();
        List<UnicodeFragment> numeralUnicodeFragmentList = UnicodeTypeEnum.Numeral.getUnicodeFragmentList();
        UnicodeFragment basic_Chinese_Character_UnicodeFragment = CnCharacterEnum.Basic_Chinese_Character.getUnicodeFragmentEnum().getUnicodeFragment();
        fragmentList.addAll(letterUnicodeFragmentList);
        fragmentList.addAll(numeralUnicodeFragmentList);
        fragmentList.add(basic_Chinese_Character_UnicodeFragment);
        for (int i = 0; i < length; i++) {
            String randomChar = getRandomCharByUnicodeFragmentList(fragmentList, random, isAbsoluteRandomness);
            str.append(randomChar);
        }
        return str.toString();
    }

    /**
     * @param unicodeFragmentList  unicode 片段集合
     * @param random               随机对象
     * @param isAbsoluteRandomness 是否是绝对随机，如果是绝对随机，就是将所有需要的字符进行一个整合，从其中挑选一个。相对随机就是首先公平的选择每一个unicode 片段，
     *                             之后从这个片段选择一个随机值。
     * @return 根据 unicode 片段的集合返回一个 字符
     */
    public static String getRandomCharByUnicodeFragmentList(List<UnicodeFragment> unicodeFragmentList, Random random, boolean isAbsoluteRandomness) {
        if (!isAbsoluteRandomness) {
            return getChar(unicodeFragmentList.get(random.nextInt(unicodeFragmentList.size())), random);
        } else {
            // 计算总权重
            int totalWeight = unicodeFragmentList.stream().mapToInt(UnicodeFragment::getCount).sum();

            // 根据权重随机选择一个UnicodeFragment
            int chosenWeight = random.nextInt(totalWeight);
            int cumulativeWeight = 0;
            UnicodeFragment chosenFragment = null;
            for (UnicodeFragment fragment : unicodeFragmentList) {
                cumulativeWeight += fragment.getCount();
                if (chosenWeight < cumulativeWeight) {
                    chosenFragment = fragment;
                    break;
                }
            }
            // 在选中的UnicodeFragment的编码范围内随机选择一个字符
            if (chosenFragment != null) {
                return getChar(chosenFragment, random);
            }
            throw new RuntimeException("没有字符被选中？怎么可能");
        }
    }

    /**
     * 校验码点
     *
     * @param codePoint 真实的码点
     * @return 校验码点
     */
    private static String validCodePoint(int codePoint) {
        if (Character.isValidCodePoint(codePoint)) {
            return String.valueOf(Character.toChars(codePoint));
        } else {
            // 处理超出char范围的码点
            return new String(Character.toChars(codePoint));
        }
    }

    /**
     * 根据 int类型得到对应的unicode编码，因为unicode 编码的范围是 2的16次方，所以，unicode大于编码大于65535 的时候一个字符就无法表示，只能使用String类型表示。
     *
     * @param str 单个字符
     * @return 根据 int类型得到对应的unicode编码点
     */
    public static int strToPoint(String str) {
        if (str == null || str.length() == 0) {
            throw new IllegalArgumentException("Input string is null r empoty.");
        }
        if (str.length() == 1) {
            // 处理单一BMP字符
            return str.codePointAt(0);
        } else if (str.length() == 2) {
            return str.codePointAt(0);
        } else {
            throw new IllegalArgumentException("Input string has an unexpected length.");
        }
    }


    /**
     * 这个是检测的。如果之后添加之后。查看得到的结果是否是对应的字符， !`_`!忘记之前是咋想的了
     *
     * @param a
     * @param list
     * @return
     */
    public static boolean check(String a, List<UnicodeFragment> list) {
        boolean flag = false;
        for (UnicodeFragment unicodeFragment : list) {
            int startCoding = unicodeFragment.getStartCoding();
            int endCoding = unicodeFragment.getEndCoding();
            int a1 = RandomCharacter.strToPoint(a);
            if (a1 >= startCoding && a1 <= endCoding) {
                flag = true;
            }
        }
        return flag;
    }
    // TODO： 调用方法的时候还需要考考一个重复的问题。 还可能存在一种情况，当调用的时候，传进来多个片段。片段有重复的话，怎么办，留一套不重复的还是。就按照重复的来。太细节了，涉及到公平与非公平了。

}